package 
{
	import com.bit101.components.Label;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BlendMode;
	import flash.display.MovieClip;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import flash.xml.XMLDocument;
	
	/**
	 * Reads maps generated with tiled.
	 * Maps should be compressed with zlib.
	 * @author Ivan
	 */
	public class TileMap extends Sprite 
	{
		public function TileMap(xml:XML):void 
		{
			this.xml = xml;
			
			var tileset:XML;
			for each (tileset in xml.tileset)
			{
				var ts:TileSet = new TileSet(tileset);
				ts.addEventListener(TileSet.LOADED, onTileSetLoaded);
				tilesets.push(ts);
			}
			
			tilewidth = xml.@tilewidth;
			tileheight = xml.@tileheight;
			orientation = xml.@orientation;
			
			blendMode = BlendMode.LAYER;
		}
		
		private function onTileSetLoaded(e:Event):void
		{
			var loadedNum:int = 0;
			for (var i:int = 0; i < tilesets.length; i++)
				if (tilesets[i].bitmapData)
					loadedNum++;
			
			if (loadedNum >= tilesets.length)
				buildMap();
		}
		
		private function buildMap():void
		{
			var results:XMLList = xml.child("layer");	
			var layer:XML;
			
			// Places every tile
			for each (layer in results)
			{
				var x:int = 0;
				var y:int = 0;
				
				var ba:ByteArray = base64ToByteArray(layer.data);
				ba.uncompress();
				ba.endian = Endian.LITTLE_ENDIAN;
				
				trace("layer " + layer.@name + ", " + ba.length + " bytes.");
				
				while (ba.position < ba.length)
				{
					var gid:int = ba.readInt();
					addTile(x++, y, gid);
					if (x >= xml.@width)
					{
						x = 0;
						y++;
					}
				}
			}
			
			// Places objects.
			var groups:XMLList = xml.child("objectgroup");
			var objectgroup:XML;
			
			for each (objectgroup in groups)
			{
				var objects:XMLList = groups.child("object");
				var object:XML;
				
				for each (object in objects)
				{
					addObject(object);
				}
			}		
			
			
			cacheAsBitmap = true;			// Dramatically improves performance.
			opaqueBackground = 0x000000; 	// No alpha - better performance.
			trace("tileNum: " + tileNum);
		}
		 
		private function addTile(x:int, y:int, gid:int):void
		{
			if (gid == 0) return;
			
			var flipped_horz:Boolean = gid < 0;
			if (flipped_horz)
			{
				gid -= Math.pow(2, 31);
			}
			var flipped_vert:Boolean = gid > 0x40000000;
			if (flipped_vert)
			{
				gid -= Math.pow(2, 30);
			}
			
			// Selects proper tileset
			var tileset:TileSet = getTileSet(gid);	
			
			// Gets tile texture.
			var bmpd:BitmapData = getTileBitmapData(tileset, gid);
			
			var sprite_x:int;
			var sprite_y:int;
			var matrix:Matrix = new Matrix();
			
			if (orientation == "isometric")
			{
				sprite_x = x * tilewidth / 2 - y * tilewidth / 2;
				sprite_y = y * tileheight / 2 + x * tileheight / 2;
			}
			else if (orientation == "orthogonal")
			{
				sprite_x = x * tilewidth;
				sprite_y = y * tileheight;
			}
			sprite_y -= tileset.height;
			
			matrix.translate(sprite_x, sprite_y);
			if (flipped_horz) matrix.scale( -1,  1);
			if (flipped_vert) matrix.scale(  1, -1);
			
			graphics.beginBitmapFill(bmpd, matrix);
			graphics.drawRect(sprite_x, sprite_y, tileset.width, tileset.height);
			graphics.endFill();
			
			tileNum++;
		}
		
		private function renderIsometric():void
		{
			
		}
		
		private function getTileBitmapData(tileset:TileSet, gid:int):BitmapData
		{
			// Copies tileset graphic.
			var bmpd:BitmapData = new BitmapData(tileset.width, tileset.height);
			var bmpx:int = ((gid - tileset.firstgid)) % (tileset.bitmapData.width / tileset.width);
			var bmpy:int = ((gid - tileset.firstgid)) / (tileset.bitmapData.width / tileset.width);
			bmpx *= tileset.width;
			bmpy *= tileset.height;
			bmpd.copyPixels(tileset.bitmapData, new Rectangle(bmpx, bmpy, tileset.width, tileset.height), new Point());
			
			return bmpd;
		}
		
		private function getTileSet(gid:uint):TileSet
		{
			var tileset:TileSet = tilesets[tilesets.length-1];
			for (var i:int = tilesets.length - 1; i >= 0; i--)
			{
				if (tilesets[i].firstgid <= gid)
				{
					tileset = tilesets[i];
					break;
				}
			}
			return tileset;
		}
		
		/*
		 * For reusability, this method should be implemented by the client of this class.
		 * */
		private function addObject(object:XML):void
		{
			trace("Object added \n " + object + "\n");
			/*
			var gid:int = object.@gid;
			if (gid == 0) return;
			
			// Selects proper tileset
			var tileset:TileSet = getTileSet(gid);
			
			var x:Number = object.@x;
			var y:Number = object.@y;
			
			x /= 32;
			y /= 32;
			
			var sprite_x:int = x * tilewidth / 2 - y * tilewidth / 2;
			var sprite_y:int = y * tileheight / 2 + x * tileheight / 2;
			sprite_y -= tileset.height;
			sprite_y -= tileheight;
			
			var bmpd:BitmapData = getTileBitmapData(tileset, gid);
			var matrix:Matrix = new Matrix();
			matrix.translate(sprite_x, sprite_y);
			graphics.beginBitmapFill(bmpd, matrix);
			graphics.drawRect(sprite_x, sprite_y, tileset.width, tileset.height);
			graphics.endFill();
			*/
		}
		
		
		private const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
		private function base64ToByteArray(data:String):ByteArray 
		{
			var output:ByteArray = new ByteArray();
			//initialize lookup table
			var lookup:Array = [];
			for(var c:int = 0; c < BASE64_CHARS.length; c++)
				lookup[BASE64_CHARS.charCodeAt(c)] = c;

			var outputBuffer:Array = new Array(3);
			
			for (var i:uint = 0; i < data.length - 3; i += 4) 
			{
				//read 4 bytes and look them up in the table
				var a0:int = lookup[data.charCodeAt(i)];
				var a1:int = lookup[data.charCodeAt(i + 1)];
				var a2:int = lookup[data.charCodeAt(i + 2)];
				var a3:int = lookup[data.charCodeAt(i + 3)];
			
				// convert to and write 3 bytes
				if(a1 < 64)
					output.writeByte((a0 << 2) + ((a1 & 0x30) >> 4));
				if(a2 < 64)
					output.writeByte(((a1 & 0x0f) << 4) + ((a2 & 0x3c) >> 2));
				if(a3 < 64)
					output.writeByte(((a2 & 0x03) << 6) + a3);
			}
			
			// Rewind & return decoded data
			output.position = 0;
			return output;
		}
		
		private var xml:XML;
		private var tilesets:Vector.<TileSet> = new Vector.<TileSet>();		
		private var tilewidth:int;
		private var tileheight:int;
		private var orientation:String;

		public var tileNum:int;
		
	}
	
}